require("coxpcall")
require("cosmo")
require'zdc.showutil'
require'zdc.idutil'
local dfskey=zdc.fieldutil.dfskey
local util = require("zwsapi.util")
local html_forms = require("zwsapi.html_forms")
local wrapper=require("zwsapi.wrapper")
module(..., package.seeall)
actions={}

function check_post_parameters(user,dal,node,act,request)
	local token = request.params.post_token
	local timestamp = request.params.post_timestamp
	local timeout = (dal.config.POST_TOKEN_TIMEOUT or 15) * 60
	if not token then
		return false, "MISSING_POST_TOKEN"
	elseif not timestamp then
		return false, "MISSING_POST_TIME_STAMP"
	elseif (os.time()-tonumber(timestamp)) > timeout then 
		return false, "YOUR_POST_TOKEN_HAS_EXPIRED"
	elseif  dal.auth:Timestamp_token(timestamp) ~= token then 
		return false, "YOUR_POST_TOKEN_IS_INVALID"
	else
		return true
	end
end

function get_node_action(dal,node,action)
	return zdc.nodeapi.getnodeact(dal,node,action) or actions.action_not_found
end
-----------------------------------------------------------------------------
-- Shows the HTML for an error message when a non-existent action is requested.
-----------------------------------------------------------------------------
function actions.action_not_found(user,dal,node,act,request)
	node.inner_html = cosmo.f(node.templates.ACTION_NOT_FOUND){
								title				 = node.title,
								url					= dal:make_url(request.node_name),
								action				= act,
								if_custom_actions = cosmo.c(false){}
							}
	return node.wrappers.default(user,dal,node,act,request)
end


-----------------------------------------------------------------------------
-- Handles all post actions.  All "post" requests are routed through this
-- action ("post"). The reason for this is that we localize button labels,
-- and their values are not predictable for this reason. Instead, we we look
-- at the _name_ the button to infer the action. So, to request node.save via
-- post, we actually request node.post&action_save=foo, where foo could be
-- anything.
--
-- @param node
-- @param request		  We'll look for request.params.action_* to figure out
--							  what we should be actually doing.
-- @return					HTML (whatever is returned by the action that it
--							  dispatches to).
-----------------------------------------------------------------------------
function actions.post(user,dal,node,act,request)
	for k,v in pairs(request.params) do
		local action = string.match(k, "^action_(.*)$")
		if action then
			function err_msg(err_code, message)
				request.try_again = "true"
				node:post_translated_error(err_code, message or "")
			end

			-- check the validity of the request
			local ok, err = check_post_parameters(user,dal,node,act,request)
			request.post_parameters_checked = true
			if not ok then
				err_msg(err)
			end

			-- test captcha, if configured
			if dal.captcha and not (request.user or request.params.user) then
				local client_ip = request.ip
				local captcha_ok, err = dal.captcha:verify(request.POST, client_ip)
				if not captcha_ok then
					err_msg("COULD_NOT_VERIFY_CAPTCHA", err)
				end
			end

			-- check if the user is allowed to do this
			if not node:__check_acl(user, action) then
				err_msg("ACTION_NOT_ALLOWED")
			end
			return get_node_action(dal,node,action)(user,dal,node,action,request)
		end
	end
end


-----------------------------------------------------------------------------
-- Saves a new node based on query params, then redirects to the node.
--
-- @param node
-- @param request		  request.params fields are used to update the node.
-- @param dal		  used to save and reload the node.  
-----------------------------------------------------------------------------

function actions.create(user,dal,node,act,request)
	if dal:HasNode(request.node_name) then
		node:post_error("Node already exists.")
		node.inner_html = ""
		return node.wrappers.default(user,dal,node,act,request)
	else
		request.create_new = true
		return get_node_action(dal,node,'save')(user,dal,node,act,request)
	end
end

-----------------------------------------------------------------------------
-- Saves/updates the node based on query params, then redirects to the new
-- version of the node.
--
-- @param node
-- @param request		  request.params fields are used to update the node.
-- @param dal		  used to save and reload the node.  
-----------------------------------------------------------------------------
function actions.save(user,dal,node,act,request)
	if request.try_again then
		return get_node_action(dal,node,'edit')(user,dal,node,act,request)
	else
		local sdn,msg=dal:GetNode(request.node_name)
		if not sdn and request.create_new then
			sdn,msg=dal:SureNode(request.node_name,nil,true)
			if not sdn then
				node:post_error("Node can't be create.")
				node.inner_html = ""
				return node.wrappers.default(user,dal,node,act,request)
			end
		end
		--TODO: 0 must instead by 1 or ?
		local setuser=user
		if node.__id=='@ws/j' then
			if user and dal.auth:Get_metadata(dal,user, "is_admin") == "true" then
				setuser=nil
			end
		end
		local err,msg=zdc.nodeapi.mutilset(setuser,dal,sdn,request.params,0)
		if not err then
			node:post_error(msg)
			node.inner_html = ""
			return node.wrappers.default(user,dal,node,act,request)
		end
		local extra = {}
		if not request.user then
			extra.ip=request.ip -- track IPs for anonymous
		end
		sdn:__save()

		-- redirect to the newly saved node
		request.redirect = dal:make_url(sdn.__id)
		return
	end
end

-----------------------------------------------------------------------------
-- Shows the HTML for an access denied message.
-----------------------------------------------------------------------------
function actions.access_denied(user,dal,node,act,request)
	node:post_error(node.translator.translate_key("ACTION_NOT_ALLOWED"))
	node.inner_html = ""
	return node.wrappers.default(user,dal,node,act,request)
end


-----------------------------------------------------------------------------
-- Returns just the content of the node, without the navigation bar, the tool
-- bars, etc.
-- 
-- @param node
-- @param request		  not used.
-- @param app		  not used.
-----------------------------------------------------------------------------
function actions.show_content(user,dal,node,act,request)
	return node.markup.transform(node.content or "", node)
end

REDIRECTION_PROTOCOLS = {
	http = true,
	https = true,
}

function actions.redirect(user,dal,node,act,request)
	local destination = node.redirect_destination
	local protocol = destination:match("^[^%:]*")
	if REDIRECTION_PROTOCOLS[protocol] or destination:sub(1,1)=="/" then
		request.redirect = destination
	else
		request.redirect = dal:make_url(destination)
	end
	return
end

function actions.show(user,dal,node,act,request)
	if node.redirect_destination and node.redirect_destination~="" then
		local _redirect=zdc.nodeapi.getnodeact(dal,node,'redirect') or actions.redirect 
		return _redirect(user,dal,node,act,request)
	end
	if node.__is_a_stub then
		request.is_indexable = false
		node.inner_html = cosmo.f(node.templates.NEW_NODE){
			icon_base_url = dal.config.ICON_BASE_URL or dal.config.NICE_URL,
			edit_url = dal:make_url(node.__id, "edit_new"),
			do_prototypes = function()
									 local prototypes = dal.config.NEW_NODE_PROTOTYPES or {}
									 for i,v in ipairs(prototypes) do
										 cosmo.yield{
											 name =v.title or v[1],
											 icon = v.icon,
											 url  =dal:make_url(node.__id, "edit_new", {_pto=v[1]})
										 }
									 end
								 end
		}
	else 
		request.is_indexable = true
		local _show_content=zdc.nodeapi.getnodeact(dal,node,'show_content') or actions.show_content
		node.inner_html = _show_content(user,dal,node,act,request)
	end
	return node.wrappers.default(user,dal,node,act,request)
end

-----------------------------------------------------------------------------
-- Forces a re-initialization of dal.
-----------------------------------------------------------------------------
function actions.reload(user,dal,node,act,request)
	dal:Init(dal._bak_config)
	node.inner_html = "Reloading..."
	request.redirect = dal:make_url(node.name)
	return
end

-----------------------------------------------------------------------------
-- Updates the node with values in query parameters, then calls show_content.
-- This has the effect of showing us what the node would look like if we
-- saved it.  Note that this action is, strictly speaking, idempotent and can
-- be called via GET.  However, it's simpler to do it with post - for
-- symmetry with "save".
--
-- @param node
-- @param dal		  request.params fields are used to update the node.
-- @param sputnik		  used to access update functionality.
-----------------------------------------------------------------------------
function actions.preview_content(user,dal,node,act,request)
	local sdn,msg=dal:GetNode(request.node_name)
	if sdn then sdn:__update_fields(request.params) end
	return get_node_action(dal,node,'show_content')(user,dal,node,act,request)
end

-----------------------------------------------------------------------------
-- Returns HTML showing a preview of the node (based on request.params) and
-- also a form to continue editing the node.  (The node is _not_ saved.)
--
-- @param request		  request.params fields are used to update the node.
-- @param dal		  passed to preview_content().
-----------------------------------------------------------------------------
function actions.preview(user,dal,node,act,request)
	request.preview = get_node_action(dal,node,'preview_content')(user,dal,node,act,request)
	return get_node_action(dal,node,'edit')(user,dal,node,act,request)
end

-----------------------------------------------------------------------------
-- Handles the clicking of the "cancel" button from the edit form.  This
-- action is idempotent and can be called via GET, but is submitted via POST,
-- for symmetry with "save".
--
-- @param node
-- @param request		  not used.
-- @param dal		  not used.
-----------------------------------------------------------------------------
function actions.cancel(user,dal,node,act,request)
	-- Redirect to the show action
	request.redirect = dal:make_url(request.node_name)
	return
end

--=========================================================================--
--	  Now the few remaining things
--=========================================================================--

function actions.configure (user,dal,node,act,request,etc)
	request.admin_edit = true
	return get_node_action(dal,node,'edit')(user,dal,node,act,request,etc)
end

-----------------------------------------------------------------------------
-- Shows HTML for the edit field for a non-existent node.
-----------------------------------------------------------------------------
function actions.edit_new (user,dal,node,act,request,etc)
	if dal:HasNode(request.node_name) then
		node:post_error("Node already exists.")
		node.inner_html = ""
		return node.wrappers.default(user,dal,node,act,request)
	else
		request.edit_new = true
		return get_node_action(dal,node,'edit')(user,dal,node,act,request, etc)
	end
end

-----------------------------------------------------------------------------
-- Shows HTML for the standard Edit field.
-----------------------------------------------------------------------------
function actions.edit (user,dal,node,act,request,etc)
	request.is_indexable = false

	etc = etc or {} -- additional parameters

	-- Add the editpage stylesheet
	node:add_javascript_link(dal:make_url("@ws/edit_scripts.js"))

	-- Select the parameters that should be copied
	local fields = {}
	for field, field_params in pairs(node._fds) do
		if not field_params.hide then
			fields[field] = dal:escape(request.params[field] or node.__raw_v[field])
		end
	end
	fields.page_name = dal:dirify(node.name)  -- node name cannot be changed
	fields.user= request.params.user or ""
	fields.password=""
	fields.summary= request.params.summary or ""

	local honeypots = "" 
	math.randomseed(os.time())
	for i=0, (dal.config.NUM_HONEYPOTS_IN_FORMS or 0) do
		local field_name = "honey"..tostring(i)
		honeypots = honeypots.."\n"..cosmo.f([[$name			 = {$order, "honeypot"}]]){
										  order = string.gsub (tostring(math.random()*5), ",", "."),
										  name  = field_name,
									  }
		fields[field_name] = ""
	end

	-- Setup the field spec
	local edit_ui_field = etc.edit_ui_field
	if request.admin_edit then
		edit_ui_field = edit_ui_field or "admin_edit_ui"
	else
		edit_ui_field = edit_ui_field or "edit_ui"
	end 

	-- Pre-compile the field spec
	local cfields, cfield_names = html_forms.compile_field_spec(node[edit_ui_field]..honeypots)
	for i, field in ipairs(cfields) do
		local editor_classes = {}
		if field.editor_modules then
			for j, module in ipairs(field.editor_modules) do
				local editor_module = require("zwsapi.editor." .. module)
				editor_module.initialize(user,dal,node,act,request)
				table.insert(editor_classes, "editor_" .. module)
			end

			local editor_class_txt = table.concat(editor_classes, " ")
			if not field.class or #field.class <= 0 then
				field.class = editor_class_txt
			else
				field.class = field.class .. editor_class_txt
			end
		end
		if cfield_names[i] == "category" then
			if field[2] == "select" then
				local _, category_hash = wrapper.get_nav_bar(node, dal)
				local categories = {{display="		 ", value=""}}
				for id, _ in pairs(category_hash) do
					categories[#categories + 1] = {value=id, display=category_hash[id]}
				end
				table.sort(categories, function(x,y) return x.display < y.display end)
				field.options = categories
			end
		end
	end


	-- Make the form

	local form_params = {
		field_spec = node[edit_ui_field]..honeypots, 
		values	  = fields,
	}
	local form = node:make_post_form(form_params, cfields, cfield_names)

	local captcha_html = ""
	if not request.user and dal.captcha then
		for _, field in ipairs(dal.captcha:get_fields()) do
			table.insert(form.field_list, field)
		end
		captcha_html = node.translator.translate_key("ANONYMOUS_USERS_MUST_ENTER_CAPTCHA")..dal.captcha:get_html()
	end

	local action="save"
	if request.edit_new then
		action = "create"
	end

	node.inner_html = cosmo.f(node.templates.EDIT){
								if_preview		= cosmo.c(request.preview){
															preview = request.preview,
															summary = fields.summary
														},
								html_for_fields = form.html_for_fields,
								node_name		 = request.node_name,
								post_fields	  = table.concat(form.field_list,","),
								post_token		= form.post_token,
								post_timestamp  = form.post_timestamp,
								action_url		= dal.config.BASE_URL,
								action			 = action,
								captcha			= captcha_html,
							}

	return node.wrappers.default(user,dal,node,act,request)
end

-----------------------------------------------------------------------------
-- Shows the raw content of the node with content-type set to text/plain (note that unlike 
-- actions.raw, this method only returns the _content_ of the node, not its metadata).
-----------------------------------------------------------------------------
function actions.raw_content(user,dal,node,act,request)
	return node.__raw_v.content, "text/plain"
end

-----------------------------------------------------------------------------
-- Shows the underlying string representation of the node as plain text.
-----------------------------------------------------------------------------
function actions.raw(user,dal,node,act,request)
	return node:__get_raw_data() or "No source available.", "text/plain"
end

-----------------------------------------------------------------------------
-- Shows the _content_ of the node shown as 'code'.
-----------------------------------------------------------------------------
function actions.show_content_as_code(user,dal,node,act,request)
	local escaped = dal:escape(node.__raw_v.content)
	if node.raw_content_type then 
		return "<pre><code class='"..node.raw_content_type.."'>"..escaped.."</code></pre>"
	else
		return "<pre><code>"..escaped.."</code></pre>"
	end
end


-----------------------------------------------------------------------------
-- Shows the complete page with it's content shown as 'code'.
-----------------------------------------------------------------------------
function actions.code(user,dal,node,act,request)
	node.inner_html =get_node_action(dal,node,'show_content_as_code')(user,dal,node,act,request, etc)
	return node.wrappers.default(user,dal,node,act,request)
end

-----------------------------------------------------------------------------
-- Shows the content of the node as Lua code, checking whether it parses.
-----------------------------------------------------------------------------
function actions.show_content_as_lua_code(user,dal,node,act,request)

	local DOLLAR_REPLACEMENT = "$<span></span>"
	local escaped = dal:escape(node.__raw_v.content)
	escaped = escaped:gsub("%$", DOLLAR_REPLACEMENT)
	escaped = escaped:gsub(" ", "&nbsp;")
	escaped = string.gsub(escaped, "(%-%-[^\n]*)", 
								 function (comment) return "<font color='gray'>"..comment.."</font>" end)
	local f, errors = loadstring(node.__raw_v.content)
	local error_line_num
	if errors then
		local reg_exp = "^.+%]%:(%d+)%:"
		error_line_num = string.match(errors, reg_exp)
		errors = string.gsub(errors, reg_exp, "On line %1:")
	end

	return cosmo.f(node.templates.LUA_CODE){
				 do_lines  = function()
									 local i = 0
									 for line in ("\n"..escaped):gmatch"\n([^\n]*)" do
										 i = i+1
										 local class = "ok"
										 if i == tonumber(error_line_num) then
											 class = "bad"
										 end
										 cosmo.yield{
											 i = i, 
											 line = line,
											 class=class
										 }
									 end
								 end,
				 if_ok	  = cosmo.c(f~=nil){},
				 if_errors = cosmo.c(errors~=nil){errors=errors},
			 }
end
-----------------------------------------------------------------------------
-- Validates a chunk of Lua code.  Returns "valid" or "invalid" depending
-- on whether the code is ok.  (This 
-----------------------------------------------------------------------------
function actions.validate_lua(user,dal,node,act,request)
	request.is_indexable = false
	local code = request.params.code or ""
	local result, err =zdc.sandbox.fast_do(dal.config,code)
	if result then
		return "valid"
	else
		-- Try to compile the code prefixed with return
		local result, err = zdc.sandbox.fast_do(dal.config,'return '..code)
		if result then
			return "valid"
		else
			return "invalid"
		end
		return "invalid" --tostring(err.err)
	end
end

function actions.apidoc(user,dal,node,act,request)
	local RN=request.node_name
	local HTML_BEGIN=[=[
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
	<title>$title</title>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
	<style>
		h1 {text-align: center;}
		table {	empty-cells: show;border-collapse:collapse;border-spacing:0;}
		TH {background: #f3f3f3;BORDER:1px solid  #dadada;  }
		TD {BORDER:1px solid  #dadada;  }	
	</style>
</head>

<body>
$index[[<h2><a href=#$tag>$name</a></h2>]]
]=]
	local HTML_END=[[
</body>
</html>
]]
	local g=require'zdc.genptodoc'
	local usp={}
	local _tsp={}
	local hitab={}
	usp[RN]=true
	local flag=true
	while flag do
		flag = false
		for k,v in pairs(usp) do
			if type(k)=='string' and type(v)~='string' then
				flag=true
				--print('do',k)
				local n=dal:GetNode(k)
				if n then
					usp[k]=g.gen_one_proto(n,usp,_tsp,hitab)
				else
					usp[k]='not find ' .. k
					--print(usp[k])
				end
			end
		end
	end
	table.sort(usp,zdc.idutil.CmpSub)
	local tsp={}
	for i,j in pairs(_tsp) do
		tsp[#tsp+1]=i
	end
	table.sort(tsp,function (a,b)
		local ac=a._class or ''
		local bc=b._class or ''
		if ac==bc then return a.name < b.name end
		return ac<bc
	end)
	for i,f in ipairs(tsp) do
		tsp[i]=g.gen_one_type(f)
	end
	
	local indextb={}
	for i,j in ipairs(usp) do
		local n=dal:GetNode(j)
		local name=(zdc.doc.get_doc(n._doc,'disp')or '') .. '(' .. n.__id .. ')'
		table.insert(indextb,{tag=j,name=name})
	end
	
	local doc= cosmo.f(HTML_BEGIN){title=request.node_name;index=indextb}
	local tmp={} 
	for i,j in ipairs(usp) do
		table.insert(tmp,usp[j])
	end
	doc=doc.. table.concat(tmp) .. table.concat(tsp) .. HTML_END
	return doc
end